﻿#region Librerias

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using ESF.DMS.FileSynchronizator.Config;
using System.Configuration;
using ESF.General;
using System.IO;
using ESF.General.Security;
using System.Threading.Tasks;
using System.Threading;
using Eloquera.Client;
using ESF.DMS.Entities.SyncFiles;
using ESF.General.Entity;

#endregion


namespace ESF.DMS.FileSynchronizator
{
	partial class ESF_DMS_FileSinchronizator : ESF.General.BaseWindowsService
	{
		#region Members

		private SynchronizedDirectoriesSection _SynchronizedDirectoriesSection;

		private List<FileSystemWatcher> _FSWs;

		#endregion


		#region Constructor

		public ESF_DMS_FileSinchronizator()
		{
			InitializeComponent();

			
			CreateLogDB ();

			_FSWs = new List<FileSystemWatcher> ();
		}

		#endregion


		#region Funciones

		private DB GetDBInstance ()
		{
			DB result;


			result = new DB ("server=(local);options=none;");

			result.OpenDatabase (ServiceName);


			return result;
		}


		private void CreateLogDB ()
		{
			using (DB db = new DB ("server=(local);options=none;"))
			{
				if (!File.Exists(Path.Combine(Log.ApplicationDirectory, ServiceName + ".eq")))
				{
					db.CreateDatabase(ServiceName);
				}

				db.OpenDatabase(ServiceName);

				db.RegisterType(typeof(CopiedFileLog));
				db.RegisterType(typeof(DeletedFileLog));
				db.RegisterType(typeof(RenamedFileLog));
				db.RegisterType(typeof(ReplacedFileLog));
				
				db.RefreshMode = ObjectRefreshMode.AlwaysReturnUpdatedValues;
			}
		}


		private void Delete (string pDestination)
		{
			Delete (new FileInfo (pDestination));	
		}


		private void Delete (FileInfo pDestination)
		{
			DeletedFileLog delFileLog;


			try
			{
				if (!pDestination.Exists)
				{
					return;
				}

				delFileLog = new DeletedFileLog () { DelFile = new FileInfoData (pDestination) };

				FileHelper.Delete (pDestination.FullName);

				using (DB db = GetDBInstance ())
				{
					db.Store (delFileLog);
				}
			}
			catch (Exception pEx)
			{
				Log.Error (pEx);
			}
		}


		private void Copy (SynchronizedDirectoryElement pItem, string pSource, string pDestination)
		{
			Copy (pItem, new FileInfo (pSource), new FileInfo (pDestination));
		}


		private void Copy (SynchronizedDirectoryElement pItem, FileInfo pSource, FileInfo pDestination)
		{
			CopiedFileLog copiedFile;

			Stopwatch sw = new Stopwatch ();


			if (!IsIncludedDirectoryFile (pItem, pSource))
			{
				return;
			}
			
			copiedFile = new CopiedFileLog ();
						
			// Archivo con cambios
			if (FileHelper.EqualsOfLengthWrite (pSource, pDestination))
			{
				return;
			}

			if (pDestination.Exists)
			{
				copiedFile.PreviousDestinationFile = new FileInfoData (pDestination);
			}

			Delete (pDestination);
			
			
			sw.Start ();
			
			try
			{
				FileHelper.Copy(pSource.FullName, pDestination.FullName);

				sw.Stop();

				copiedFile.DestinationFile = new FileInfoData(pDestination.FullName);
				copiedFile.CopyEllapsedTimeMs = sw.ElapsedMilliseconds;

				using (DB db = GetDBInstance ())
				{
					db.Store(copiedFile);
				}
			}
			catch (Exception pEx)
			{
				Log.Error(pEx);
			}			
		}


		private bool IsIncludedDirectoryFile (SynchronizedDirectoryElement pItem, string pSource)
		{
			return IsIncludedDirectoryFile (pItem, new FileInfo (pSource));
		}
		

		private bool IsIncludedDirectoryFile (SynchronizedDirectoryElement pItem, FileInfo pSource)
		{
			if (!TextHelper.IsIn (Path.GetExtension (pSource.FullName), true, pItem.fileExtensionsValue) 
				|| !pSource.FullName.StartsWith(pItem.sourceDirectoryPath))
			{
				return false;
			}
			
			if (pItem.startDateTimeFileValue != null
				&& DateTime.Compare(pSource.LastWriteTime, pItem.startDateTimeFileValue.Value) < 0)
			{
				return false;
			}


			if (pItem.startDateFolderValue != null && pItem.startDateFolderDepth > 0)
			{
				 if (!IsIncludedDirectory (pSource.DirectoryName, pItem))
				 {
					return false;
				 }
			}

			return true;
		}


		private bool IsIncludedDirectory (string pDir, SynchronizedDirectoryElement pItem)
		{
			DateTime? folderNameDate;

			string[] directoryParts;
			
			
			if (pDir.EndsWith (Path.DirectorySeparatorChar.ToString ()))
			{
				pDir = pDir.Substring (0, pDir.Length - 1);
			}

			directoryParts = FileHelper.SplitWithoutUnitLabel (pDir);

			if (directoryParts.Length >= pItem.startDateFolderDepth)
			{
				folderNameDate = TextHelper.GetNullableDateTime(directoryParts[pItem.startDateFolderDepth - 1], pItem.startDateFolderFormat);

				if (folderNameDate != null && DateTime.Compare(folderNameDate.Value, pItem.startDateFolderValue.Value) >= 0)
				{
					return true;
				}
			}


			return false;
		}


		private void SynchronizeDirectories (SynchronizedDirectoryElement pItem)
		{
			Stopwatch sw = new Stopwatch ();


			sw.Start ();

			Log.WriteLineHistory ("Synchronizing directories for {0}", pItem.name);
			
			//using (Impersonator imp = new Impersonator(pItem.userName, pItem.userDomain, pItem.userPassword))
			//{	
				// Se navegan solo los directorios del nivel esperado que superen la validacion
				if (pItem.startDateFolderDepth > 0 && pItem.startDateFolderValue != null)
				{
					foreach (string dir in FileHelper.ListDirectories (pItem.sourceDirectoryPath, pItem.startDateFolderDepth,
						(string pDir) => { return IsIncludedDirectory (pDir, pItem); }))
					{
						FileHelper.ExecuteInDirectoryFiles (dir,
							(FileInfo pOriginalFilePath, object[] pParams) =>
							{
								FileInfo destinationFileInfo;


								destinationFileInfo = new FileInfo (
									Path.Combine(pItem.destinationDirectoryPath,
										pOriginalFilePath.FullName.Substring(pItem.sourceDirectoryPath.Length)));

								Copy(pItem, pOriginalFilePath, destinationFileInfo);

							}, pFileExtensions: pItem.fileExtensionsValue);
					}
				}
				else
				{
					// Se navegan los archivos originales para replicarlos
					FileHelper.ExecuteInDirectoryFiles (pItem.sourceDirectoryPath,
						(FileInfo pOriginalFilePath, object [] pParams) =>
						{
							FileInfo destinationFileInfo;

					
							destinationFileInfo = new FileInfo (
								Path.Combine(pItem.destinationDirectoryPath,
									pOriginalFilePath.FullName.Substring(pItem.sourceDirectoryPath.Length)));

							Copy (pItem, pOriginalFilePath, destinationFileInfo);

						}, pFileExtensions : pItem.fileExtensionsValue);
				}


			//using (Impersonator imp = new Impersonator(pItem.userName, pItem.userDomain, pItem.userPassword))
			//{
				// Se navegan los archivos destino para verificar diferencias y eliminarlas
				FileHelper.ExecuteInDirectoryFiles(pItem.destinationDirectoryPath,
					(FileInfo pDestination, object[] pParams) =>
					{					
						FileInfo sourceFileInfo;


						sourceFileInfo = new FileInfo(
							Path.Combine(pItem.sourceDirectoryPath,
								pDestination.FullName.Substring(pItem.destinationDirectoryPath.Length)));

						// Archivo con cambios
						if (!sourceFileInfo.Exists)
						{
							Delete (pDestination);
						}

					}, pFileExtensions: pItem.fileExtensionsValue);
			//}

			sw.Stop ();

			Log.WriteLineHistory("Synchronizing directories for {0} was completed in {1}", pItem.name, TextHelper.ToStringMS (sw.ElapsedMilliseconds));
		}


		private FileSystemWatcher GenerateFSWSource (SynchronizedDirectoryElement pItem)
		{
			//using (Impersonator imp = new Impersonator(pItem.userName, pItem.userDomain, pItem.userPassword))
			//{
				FileSystemWatcher fsw;


				fsw = new FileSystemWatcher();

				fsw.IncludeSubdirectories = true;

				fsw.Path = pItem.sourceDirectoryPath;

				fsw.Renamed += new RenamedEventHandler(fswSource_Renamed);
				fsw.Changed += new FileSystemEventHandler(fswSource_Changed);
				fsw.Error += new ErrorEventHandler(fsw_Error);
				fsw.Deleted += new FileSystemEventHandler(fsw_Deleted);
				fsw.Created += new FileSystemEventHandler(fsw_Created);	

				fsw.EnableRaisingEvents = true;

				return fsw;
			//}
		}

		
		private void fsw_Created(object sender, FileSystemEventArgs e)
		{
			// Sincronizar todos los destinos 

			string destinationResultFilePath;
			

			foreach (SynchronizedDirectoryElement item in _SynchronizedDirectoriesSection.SynchronizedDirectories)
			{
				//using (Impersonator imp = new Impersonator(item.userName, item.userDomain, item.userPassword))
				//{
				destinationResultFilePath =
					Path.Combine(item.destinationDirectoryPath,
						e.FullPath.Substring(item.sourceDirectoryPath.Length));

				Copy (item, e.FullPath, destinationResultFilePath);	
				//}
			}
		}


		private void fsw_Deleted(object sender, FileSystemEventArgs e)
		{
			string destinationResultFilePath;


			foreach (SynchronizedDirectoryElement item in _SynchronizedDirectoriesSection.SynchronizedDirectories)
			{
				if (!IsIncludedDirectoryFile(item, e.FullPath))
				{
					return;
				}
				
				
				destinationResultFilePath =
					Path.Combine(item.destinationDirectoryPath,
						e.FullPath.Substring(item.sourceDirectoryPath.Length));

				Delete(destinationResultFilePath);
			}
		}


		private void fswSource_Renamed(object sender, RenamedEventArgs e)
		{
			RenamedFileLog renamedFile;

			FileInfo sourceFile;
			FileInfo destinationOldFile;
			FileInfo destinationFile;
			

			foreach (SynchronizedDirectoryElement item in _SynchronizedDirectoriesSection.SynchronizedDirectories)
			{
				if (!IsIncludedDirectoryFile(item, e.FullPath))
				{
					return;
				}

				try
				{
					renamedFile = new RenamedFileLog ();

					sourceFile = new FileInfo (e.FullPath);

					destinationOldFile = 
						new FileInfo (Path.Combine (item.destinationDirectoryPath, 
							e.OldFullPath.Substring (item.sourceDirectoryPath.Length)));

					destinationFile =
						new FileInfo (Path.Combine(item.destinationDirectoryPath,
							e.FullPath.Substring(item.sourceDirectoryPath.Length)));


					if (FileHelper.EqualsOfLengthWrite (sourceFile, destinationOldFile))
					{
						destinationOldFile.MoveTo (renamedFile.RenFile.FullName);
						return;
					}

					Copy(item, sourceFile, destinationFile);
				}
				catch (Exception pEx)
				{
					Log.Error (pEx);
				}
			}
		}


		private void fswSource_Changed(object sender, FileSystemEventArgs e)
		{
			// Sincronizar todos los destinos 
			string destinationResultFilePath;


			// Si se reporta el directorio se omite, solo se cambian los archivos
			if (Directory.Exists (e.FullPath))
			{
				return;
			}


			foreach (SynchronizedDirectoryElement item in _SynchronizedDirectoriesSection.SynchronizedDirectories)
			{
				if (!IsIncludedDirectoryFile(item, e.FullPath))
				{
					return;
				}
					
				destinationResultFilePath =
					Path.Combine(item.destinationDirectoryPath,
						e.FullPath.Substring(item.sourceDirectoryPath.Length));

				Delete (destinationResultFilePath);

				if (e.ChangeType == WatcherChangeTypes.Deleted)
				{
					continue;
				}
					
				Copy (item, e.FullPath, destinationResultFilePath);
			}
		}


		private void fsw_Error(object sender, ErrorEventArgs e)
		{
			Log.Error(e.GetException());
		}

		#endregion


		#region Eventos

		public override void DoStart()
		{
			using (DB db = GetDBInstance ())
			{
				Log.WriteLineHistory ("Current there are {0} of registers of copied files", (from CopiedFileLog cfl in db select cfl).Count ());
			}


			_SynchronizedDirectoriesSection = ConfigurationManager.GetSection("SynchronizedDirectoriesSection") as SynchronizedDirectoriesSection;


			if (_SynchronizedDirectoriesSection == null || _SynchronizedDirectoriesSection.SynchronizedDirectories.Count == 0)
			{
				return;
			}

			Log.WriteLineHistory("Synchronizable directories: {0}", _SynchronizedDirectoriesSection.SynchronizedDirectories.Count);

			foreach (SynchronizedDirectoryElement item in _SynchronizedDirectoriesSection.SynchronizedDirectories)
			{
				Log.WriteLineHistory("Processing item with Name: {0}, Source: {1}, Destination: {2}, startDateTimeFile: {3}, startDateFolder: {4}, startDateFolderDepth: {5}",
					item.name, item.sourceDirectoryPath, item.destinationDirectoryPath, 
					TextHelper.ToString(item.startDateTimeFile), 
					TextHelper.ToString(item.startDateFolder, TextHelper.DEFAULT_DATE_FORMAT), 
					item.startDateFolderDepth,
					item.DoImpersonateUser ? string.Format (", User: {0}, Password: {1}, Domain: {2}", item.userName, item.userPassword, item.userDomain) : string.Empty);

					
				if (!_FSWs.Exists (fsw => fsw.Path == item.sourceDirectoryPath))
				{
					_FSWs.Add (GenerateFSWSource (item));

					Task.Factory.StartNew (() => { SynchronizeDirectories(item); });
				}
			}
		}


		public override void DoStop()
		{
			if (!DataHelper.IsEmpty (_FSWs))
			{
				foreach (FileSystemWatcher fsw in _FSWs)
				{
					fsw.EnableRaisingEvents = false;
				}
			}
		}

		#endregion
	}
}
